package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/boltdb/bolt"
)

type Store interface {
	Get([]Condition) ([]map[string]string, error)
	Close() error
}

func NewStore(basepath, dbname string) (s Store, err error) {
	s, err = NewBoltStore(basepath, dbname)
	return
}

type BoltStore struct {
	fn string
	db *bolt.DB
}

func NewBoltStore(basepath, dbname string) (Store, error) {
	fn := path.Join(basepath, dbname)
	db, err := bolt.Open(fn, 0600, nil)
	if err != nil {
		return nil, err
	}
	return &BoltStore{
		fn: fn, db: db,
	}, nil
}

func GetBoltStore(basepath, dbname string) (Store, error) {
	fn := path.Join(basepath, dbname)
	_, err := os.Stat(fn)
	if os.IsNotExist(err) {
		return nil, err
	}
	db, err := bolt.Open(fn, 0600, nil)
	if err != nil {
		return nil, err
	}
	return &BoltStore{
		fn: fn, db: db,
	}, nil
}

func PrepareStore(basepath string, month int) map[string]map[string]Store {
	tables := make(map[string]map[string]Store)
	now := time.Now()
	nMonthAgo := now.AddDate(0, (-1)*month, 0)
	dates := getDates(nMonthAgo, now)
	// path: ./date/dbname
	for _, date := range dates {
		dbpath := path.Join(basepath, date)
		dbDir, err := ioutil.ReadDir(dbpath)
		if err != nil {
			continue
		}
		tables[date] = make(map[string]Store)
		for _, dbfile := range dbDir {
			dbname := dbfile.Name()
			db, err := NewStore(dbpath, dbname)
			if err != nil {
				log.Println("[Error]: Error occurs while opening db: " + date + "/" + dbname)
				return nil
			}
			tables[date][dbname] = db
		}
	}
	return tables
}

func Put() {
	c := 0
	ReadLine("post.log", func(line string) {
		table := strings.Split(line, "tablename=")[1]
		table = strings.Split(table, "&")[0]
		data := strings.Split(line, "data=")[1]
		data = strings.Split(data, " HTTP/")[0]
		str, _ := url.QueryUnescape(data)
		str = strings.Replace(str, "'", "\"", -1)
		var s []map[string]string
		json.Unmarshal([]byte(str), &s)
		for _, record := range s {
			ip := record["IP"]
			staff := record["STAFF_ID"]
			d := record["DATETIME"]
			t, _ := time.Parse("20060102030405", d)
			date := t.Format("2006-01-02")
			if _, err := os.Stat(date); os.IsNotExist(err) {
				os.MkdirAll(date, 0755)
			}
			db, _ := bolt.Open(date+"/"+table, 0600, nil)
			_ = db.Update(func(tx *bolt.Tx) error {
				fmt.Println(staff + " " + ip + " " + d)
				buck, _ := tx.CreateBucketIfNotExists([]byte(staff))
				buck, _ = buck.CreateBucketIfNotExists([]byte(ip))
				buck, _ = buck.CreateBucketIfNotExists([]byte(d))
				for k, v := range record {
					_ = buck.Put([]byte(k), []byte(v))
				}
				c++
				return nil
			})
			db.Close()
		}
	})
	fmt.Println(c)
}

func ReadLine(fileName string, handler func(string)) error {
	f, err := os.Open(fileName)
	defer f.Close()
	if err != nil {
		return err
	}
	buf := bufio.NewReader(f)
	for {
		line, err := buf.ReadString('\n')
		line = strings.TrimSpace(line)
		handler(line)
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
	}
	return nil
}

func (b *BoltStore) Close() error {
	return b.db.Close()
}

func (b *BoltStore) Get(cnds []Condition) ([]map[string]string, error) {
	var err error
	var result []map[string]string
	err = b.db.View(func(tx *bolt.Tx) error {
		return tx.ForEach(func(staff []byte, bucket *bolt.Bucket) error {
			// STAFF_ID->IP->DATETIME -> KV
			staffCur := bucket.Cursor()
			for ip, _ := staffCur.First(); ip != nil; ip, _ = staffCur.Next() {
				ip_bkt := bucket.Bucket(ip)
				ipCur := ip_bkt.Cursor()
				for date, _ := ipCur.First(); date != nil; date, _ = ipCur.Next() {
					date_bkt := ip_bkt.Bucket(date)
					match := true
					for _, cond := range cnds {
						// change time to unix second
						if cond.key == "STAFF_ID" {
							cond.key = string(staff)
						} else if cond.key == "IP" {
							cond.key = string(ip)
						} else if cond.key == "DATETIME" {
							t := string(date) + "000"
							if left, err1 := Time2Unix(t); err1 != nil {
								log.Println("[Error]: Parse time error: " + t)
								return ParseArgsErr
							} else {
								cond.key = strconv.FormatInt(left, 10)
							}
							if right, err2 := Time2Unix(cond.val); err2 != nil {
								log.Println("[Error]: Parse time error: " + cond.val)
								return ParseArgsErr
							} else {
								cond.val = strconv.FormatInt(right, 10)
							}
						} else {
							cond.key = string(date_bkt.Get([]byte(cond.key)))
						}
						// TODO 对key错误处理
						if cond.key == "" {
							log.Printf("[Error]: Key %v not found\n" + cond.key)
							return KeyNotFound
						}
						var flag bool
						var cmperr error
						if cond.op == ">" {
							flag, cmperr = GreaterThan(cond.key, cond.val)
						} else if cond.op == "=" {
							if cond.key == "DATETIME" {
								flag, cmperr = Equal(cond.key, cond.val)
							} else {
								flag = (cond.key == cond.val)
							}
						} else if cond.op == "<" {
							flag, cmperr = LessThan(cond.key, cond.val)
						} else {
							log.Printf("[Error]: Parse query condition error: %v", cond)
							return ParseArgsErr
						}
						if cmperr != nil {
							return ParseArgsErr
						}
						match = match && flag
					}

					if match == true {
						element := make(map[string]string)
						element["STAFF_ID"] = string(staff)
						element["POST_ID"] = string(date_bkt.Get([]byte("POST_ID")))
						element["QUERY_TYPE1"] = string(date_bkt.Get([]byte("QUERY_TYPE1")))
						element["QUERY_TYPE2"] = string(date_bkt.Get([]byte("QUERY_TYPE2")))
						element["QUERY_VALUE2"] = string(date_bkt.Get([]byte("QUERY_VALUE2")))
						element["QUERY_VALUE1"] = string(date_bkt.Get([]byte("QUERY_VALUE1")))
						element["IP"] = string(ip)
						element["DATETIME"] = string(date)
						element["ORG_ID"] = string(date_bkt.Get([]byte("ORG_ID")))
						result = append(result, element)

					}
				}
			}

			return nil
		})

	})
	if err != nil {
		return nil, err
	}
	return result, nil
}

func LessThan(a, b string) (bool, error) {
	var m, n int
	var err1, err2 error
	if m, err1 = strconv.Atoi(a); err1 != nil {
		log.Println("[Error]: Fail to parse string to int: " + a)
		return false, err1
	}
	if n, err2 = strconv.Atoi(b); err2 != nil {
		log.Println("[Error]: Fail to parse string to int: " + b)
		return false, err2
	}
	return m < n, nil
}

func Equal(a, b string) (bool, error) {
	var m, n int
	var err1, err2 error
	if m, err1 = strconv.Atoi(a); err1 != nil {
		log.Println("[Error]: Fail to parse string to int: " + a)
		return false, err1
	}
	if n, err2 = strconv.Atoi(b); err2 != nil {
		log.Println("[Error]: Fail to parse string to int: " + b)
		return false, err2
	}
	return m == n, nil
}

func GreaterThan(a, b string) (bool, error) {
	var m, n int
	var err1, err2 error
	if m, err1 = strconv.Atoi(a); err1 != nil {
		log.Println("[Error]: Fail to parse string to int: " + a)
		return false, err1
	}
	if n, err2 = strconv.Atoi(b); err2 != nil {
		log.Println("[Error]: Fail to parse string to int: " + b)
		return false, err2
	}
	return m > n, nil
}
